package br.ufpe.cin.imlm2.jbeat.model.factory;

import java.util.ArrayList;
import java.util.List;

import br.ufpe.cin.imlm2.jbeat.model.ILightWeightType;
import br.ufpe.cin.imlm2.jbeat.model.LightWeightClass;
import br.ufpe.cin.imlm2.jbeat.model.LightWeightReferenceType;
import br.ufpe.cin.imlm2.jbeat.model.LightWeightType;
import br.ufpe.cin.imlm2.jbeat.model.LightWeightVectorType;

/**
 * A factory like class used to created {@link LightWeightType} instances.
 * The purpose of this class is to guarantee that only a single instance of a type with a given
 * name exists. Every time a client tries to retrieve a type either a new one is created and returned
 * or a previously created instance is returned.
 * @author irineu
 *
 */
public class TypeMaker {

	
	private static final ILightWeightType VOID_ = new LightWeightType("void");
	private static final ILightWeightType CHAR_ = new LightWeightType("char");
	private static final ILightWeightType BYTE_ = new LightWeightType("byte");
	private static final ILightWeightType SHORT_ = new LightWeightType("short");
	private static final ILightWeightType INT_ = new LightWeightType("int");
	private static final ILightWeightType LONG_ = new LightWeightType("long");
	private static final ILightWeightType FLOAT_ = new LightWeightType("float");
	private static final ILightWeightType DOUBLE_ = new LightWeightType("double");
	private static final ILightWeightType BOOLEAN_ = new LightWeightType("boolean");
	
	private List<ILightWeightType> types;
	
	public TypeMaker() {
		this.types = new ArrayList<ILightWeightType>();
		this.types.add(VOID_);
		this.types.add(CHAR_);
		this.types.add(BYTE_);
		this.types.add(SHORT_);
		this.types.add(INT_);
		this.types.add(LONG_);
		this.types.add(FLOAT_);
		this.types.add(DOUBLE_);
		this.types.add(BOOLEAN_);
	}
	
	/**
	 * Retrieves a type with <code>fullyQualifiedName</code> name.
	 * If such type doesn't exist a new one is created. 
	 * @param fullyQualifiedName
	 * @return
	 */
	public ILightWeightType retrieveType(String fullyQualifiedName){
		ILightWeightType t;
		if((t = this.getType(fullyQualifiedName)) == null){
			t = new LightWeightType(fullyQualifiedName);
			this.types.add(t);
		}
		return t;
	}
	
	/**
	 * Retrieves a <code>ILightWeightType</code> that can be safely casted to
	 * <code>LightWeightVectorType</code>. If it doesn't exist a new one is created with dimension
	 * <code>dimensions</code> and component type <code>componentType</code>.
	 * If a type with name <code>fullyQualifiedName</code> was already created by this maker but it
	 * is not a <code>LightWeightVectorType</code> then an IllegalArgumentException will be raised.
	 * @param fullyQualifiedName
	 * @param dimensions
	 * @param componentType
	 * @return
	 */
	public ILightWeightType retrieveVectorType(ILightWeightType componentType, int dimensions)
		throws IllegalArgumentException{
		verifyComponentType(componentType);
		ILightWeightType t;
		if((t = this.getVectorType(componentType.getFullyQualifiedName(),dimensions)) == null) {
			t = new LightWeightVectorType(componentType, dimensions);
			this.types.add(t);
		}
			
		return t;
	}
	
	/**
	 * Retrieves a <code>ILightWeightType</code> that can be safely casted to
	 * <code>LightWeightReferenceType</code>. If it doesn't exist a new one is created.
	 * If a type with name <code>fullyQualifiedName</code> was already created by this maker but it
	 * is not a <code>LightWeightReferenceType</code> then an IllegalArgumentException will be raised.
	 * @param fullyQualifiedName Fully qualified name of this type.
	 * @param c Possible LightWeightClass associated with this type.
	 * @return
	 */
	public ILightWeightType retrieveReferenceType(String fullyQualifiedName,LightWeightClass c) 
		throws IllegalArgumentException{
		ILightWeightType t;
		if((t = this.getType(fullyQualifiedName)) == null) {
			t = new LightWeightReferenceType(fullyQualifiedName);
			if(c != null)
				((LightWeightReferenceType)t).setThisClass(c);
			this.types.add(t);
		} else {
			if(!(t instanceof LightWeightReferenceType))
				throw new IllegalArgumentException("A type with name " + fullyQualifiedName + 
						" was already created by this maker and it is not a LightWeightReferenceType.");
		}
			
		return t;
	}

	private void verifyComponentType(ILightWeightType componentType) {
		if(!this.types.contains(componentType))
			throw new IllegalArgumentException("Component type " + 
					componentType.getFullyQualifiedName() + " wasn't not created by this TypeMaker.");
	}
	
	private ILightWeightType getType(String fullyQualifiedName){
		for (ILightWeightType t : this.types) {
			if(t.getFullyQualifiedName().equals(fullyQualifiedName))
				return t;
		}
		return null;
	}
	
	private ILightWeightType getVectorType(String componentName,int dimensions){
		for (ILightWeightType t : this.types) {
			if(t instanceof LightWeightVectorType){
				LightWeightVectorType vType = (LightWeightVectorType) t;
				if(vType.getComponentType().getFullyQualifiedName().equals(componentName) && 
						vType.getNumberOfDimensions() == dimensions)
					return t;
			}
		}
		return null;
	}
	
	/**
	 * Retrieves all types ever created by this maker.
	 * @return
	 */
	public List<ILightWeightType> retrieveTypes(){
		return this.types;
	}
}
